//
// Created by Sorel on 2021/6/11.
//

#ifndef BASIC_EAS_DE_HPP
#define BASIC_EAS_DE_HPP

#include <vector>

#include "boost_utils.hpp"
#include "cec_utils.hpp"

using namespace std;

namespace DE {

    class Individual {
    public:
        vector<double> value;
        double fitness;

        void init(int dimension, double lower_bound, double upper_bound);
        void clear();
        void print(bool only_fitness);

        Individual& operator = (const Individual&);
    };

    void Individual::init(int dimension, double lower_bound, double upper_bound) {
        this->value.clear();
        this->value.resize(dimension);
        for (auto &v : this->value) {
            v = random_double(lower_bound, upper_bound);
        }
        this->fitness = 0.0;
    }

    void Individual::clear() {
        this->value.clear();
        this->fitness = 0.0;
    }

    void Individual::print(bool only_fitness) {
        if (!only_fitness) {
            for (double i : value) {
                cout<<i<<", ";
            } cout<<". ";
        }
        cout<<"fitness: "<<fitness<<endl;
    }

    Individual &Individual::operator=(const Individual &src) {
        this->clear();
        this->value.assign(src.value.begin(), src.value.end());
        this->fitness = src.fitness;
        return *this;
    }


    class BasicDE {
    private:
        int dimension;
        double lower_bound, upper_bound;

        int pop_size;
        vector<Individual> population;
        vector<Individual> new_pop;
        Individual gBest;
        double F;
        double CR;

        string save_path;
        vector<double> value_list;  // record the history of gBest fitness
        void save_results();

    public:
        void set_save_path(const string& _save_path);
        void init(int _pop_size, int _dimension, double factor, double _cross_prob);
        void set_bounds(double _lower, double _upper);
        void execute(int func_num, int depth, int trial);
    };

    void BasicDE::set_save_path(const string &_save_path) {
        this->save_path = _save_path;
        string file_name = save_path;
        ofstream out_file(file_name);
        if (!out_file.is_open()) {
            cout<<"save_path invalid, please check directories. "<<endl;
            exit(-1);
        }
        out_file.close();
    }

    void BasicDE::init(int _pop_size, int _dimension, double factor, double _cross_prob) {
        this->pop_size = _pop_size;
        this->dimension = _dimension;
        this->F = factor;
        this->CR = _cross_prob;
    }

    void BasicDE::set_bounds(double _lower, double _upper) {
        this->lower_bound = _lower;
        this->upper_bound = _upper;
    }

    void BasicDE::execute(int func_num, int depth, int trial) {
        value_list.clear();
        value_list.resize(depth);
        int _trial = 0;
        cout<<"trial ";
        int i, j, r;
        int a, b, c;
        while (_trial < trial) {
            // reset population
            population.clear();
            new_pop.clear();
            population.resize(pop_size);
            new_pop.resize(pop_size);
            for (i = 0; i < pop_size; i++) {
                population[i].clear();
                population[i].init(dimension, lower_bound, upper_bound);
                cec14_test_func(population[i].value.data(), &population[i].fitness, dimension, 1, func_num);
            }
            gBest = population[0];
            // evolution progress
            for (int gen = 0; gen < depth; ++gen) {
                for (i = 0; i < pop_size; i++) {
                    new_pop[i] = population[i];
                }
                for (i = 0; i < pop_size; i++) {
                    // pick distinct a, b, c from population
                    do {
                        a = random_int(0, pop_size-1);
                    } while(a == i);
                    do {
                        b = random_int(0, pop_size-1);
                    } while(b == i || b == a);
                    do {
                        c = random_int(0, pop_size-1);
                    } while(c == i || c == b || c == a);
                    // crossover
                    r = random_int(0, dimension-1);
                    for (j = r; j < dimension; ++j) {
                        if (random_p() < CR) {
                            new_pop[i].value[j] = new_pop[a].value[j]
                                                  + F * (new_pop[b].value[j] - new_pop[c].value[j]);
                        }
                    }
                    cec14_test_func(new_pop[i].value.data(), &new_pop[i].fitness, dimension, 1, func_num);
                    // substitution
                    if (new_pop[i].fitness < population[i].fitness) {
                        population[i] = new_pop[i];
                    }
                }
                for (i = 0; i < pop_size; i++) {
                    if (population[i].fitness < gBest.fitness) {
                        gBest = population[i];
                    }
                }
                value_list[gen] += gBest.fitness;
            }
            _trial++;
            cout<<_trial<<", ";
        } cout<<" all trials completed. ";
        for (auto &value : value_list) {
            value = value / trial;
        }
        save_results();

    }

    void BasicDE::save_results() {
        string file_name = save_path;
        ofstream out_file(file_name);
        if (!out_file.is_open()) {
            cerr<<"save_path invalid, please check directories. "<<endl;
            exit(-1);
        }
        for (double value : value_list) {
            out_file<<value<<endl;
        }
        out_file.close();
        cout<<save_path<<" created. "<<endl;
    }
}


#endif //BASIC_EAS_DE_HPP
